package edu.hawaii.jmotif.sequitur.logic;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.StringTokenizer;
import edu.hawaii.jmotif.sax.datastructures.SAXFrequencyData;
import edu.hawaii.jmotif.sax.datastructures.SAXFrequencyEntry;

/**
 * 
 * Handling of SAX and Sequitur
 * 
 * this class utilizes a kind of  inversion of the SAXFrequencyData class where for a given
 * subsequence the associated indices are stored.
 * Here for a given index the assciated subsequence is stored.
 * 
 * @author Manfred Lerner
 *
 */
public class SAXString {
	ArrayList<SAXSubsequence> arrSubsequences = new ArrayList<SAXSubsequence>();
	String separator;
	
	/**
	 * @param separator string to separate the subsequences from each other 
	 */
	public SAXString(String separator){
		this.separator = separator;
	}
	
	/**
	 * @param arrSubsequences list of subsequences forming the SAX string
	 * @param separator string to separate the subsequences from each other
	 */
	public SAXString(ArrayList<SAXSubsequence> arrSubsequences, String separator){
		this.arrSubsequences = arrSubsequences;
		this.separator = separator;
	}
	
	/**
	 * @param freqData frequency data from the jmotif core
	 * @param separator string to separate the subsequences from each other
	 */
	public SAXString(SAXFrequencyData freqData, String separator){
		convertFrequencies2Subsequences(freqData);
		this.separator = separator;
	}
	
	/**
	 * @param freqData frequency data from the jmotif core
	 */
	protected void convertFrequencies2Subsequences(SAXFrequencyData freqData){
		HashMap<Integer, String> hashSAX = new HashMap<Integer, String>();
		ArrayList<Integer> allIndizesArr = new ArrayList<Integer>();
		
		// iterate over the frequency data
		Iterator<SAXFrequencyEntry> freqIterator = freqData.iterator();
		while (freqIterator.hasNext()) {
			SAXFrequencyEntry freqEntry = freqIterator.next();
			
			/* 
			 * for each frequency entry (=SAX subsequence):
			 * get the associated indices and store them with their subsequence in a hash map
			 */
			ArrayList<Integer> indizesArr = freqEntry.getEntries();
			for(int index : indizesArr){
				hashSAX.put(index, freqEntry.getSubstring());
			}
			// add the indices for a subsequence to the list with all indices
			allIndizesArr.addAll(indizesArr);
		}
		// sort the list with all indices
		Collections.sort(allIndizesArr,new Comparator<Integer>() {
		        public int compare(Integer int1, Integer int2) {
		            return Integer.valueOf(int1).compareTo(Integer.valueOf(int2));
		        }
		    });
		
		/*
		 * for each entry of the list with all indices:
		 * store the index and the associated subsequence in a SAXSubsequence object 
		 * add SAXSubsequence the the list with all SAXSubsequences
		 */
		for(int index : allIndizesArr){
			SAXSubsequence subseq = new SAXSubsequence();
			subseq.setPosIndex(index);
			subseq.setSubsequence(hashSAX.get(index));
			arrSubsequences.add(subseq);
		}
	}
	
	/**
	 * @param windowSize SAX window size
	 * @param paaSize SAX PAA size
	 * @param rule expanded sequitur rule
	 * @return
	 */
	public ArrayList<SAXPosition> getRulePositions(int windowSize, int paaSize, String rule){
		ArrayList<SAXPosition> arrPos = new ArrayList<SAXPosition>();
		ArrayList<String> arrSubs = getSubsequencesFromRule(rule);
		
		String strSAX = getSAXString();
		int index = strSAX.indexOf(rule);
		
		/*
		 * as long as the rule is found in the complete string of SAX subsequences,
		 * its offset position is calculated and stored in a list of SAXPosition objects
		 */
		while(index != -1){
			SAXSubsequence subStart = arrSubsequences.get(index/(paaSize+separator.length()));
			int offset = arrSubs.size() + index/(paaSize+separator.length());
			
			if( offset >= arrSubsequences.size() )offset = arrSubsequences.size()-1;
			
			SAXSubsequence subEnd = arrSubsequences.get(offset);
			
			SAXPosition saxPos = new SAXPosition();
			saxPos.setStartPos(subStart.getPosIndex());
			/*
			 * VERY IMPORTANT:
			 * at the end we have to add the window size to the end position to get a valid 
			 * mapping to the position in the original time series  
			 */
 			saxPos.setEndPos(subEnd.getPosIndex()+windowSize);
			arrPos.add(saxPos);
			index = strSAX.indexOf(rule, index + rule.length());
		}		
		
		return arrPos;
	}
	
	/**
	 * @param chartData data container 
	 * @param pos position marked in the chart
	 * @return the rule at the marked position
	 */
	public String getRuleFromPosition(MotifChartData chartData, int pos){
		
		ArrayList<SAXPosition> arrPos = new ArrayList<SAXPosition>();
		ArrayList<String> ruleList = chartData.getCurrentRule().getExpandedRuleList();
		
		// the first rule must be removed since it contains all rules
		ruleList.remove(0);
		
		for(String currentRule : ruleList){
			currentRule = chartData.convert2OriginalSAXAlphabet('1', currentRule);
			arrPos = getRulePositions(chartData.getSAXWindowSize(), chartData.getSAXPaaSize(), currentRule);
			for(SAXPosition saxPos : arrPos){
				if( pos >= saxPos.getStartPos() && pos <= saxPos.getEndPos())
					return currentRule;
			}
		}
		return null;
	}
	
	/**
	 * @param rule rule where the single subsequences should be extracted from
	 * @return list with the single subsequences of a rule
	 */
	protected ArrayList<String> getSubsequencesFromRule(String rule){
		StringTokenizer st = new StringTokenizer(rule, separator);
		ArrayList<String> arrSubs = new ArrayList<String>();
		while(st.hasMoreTokens()){
			arrSubs.add(st.nextToken());
		}
		return arrSubs;
	}
	
	/**
	 * @return string containing all subsequences separated by the separator string
	 */
	public String getSAXString(){
		StringBuilder sb = new StringBuilder();
		
		for(SAXSubsequence subsequence: arrSubsequences){
			sb.append(subsequence.getSubsequence());
			sb.append(separator);
		}
		return sb.toString();
	}
}
